/* 
  Copyright (c) Whitemice Consulting, 2007, 2008, 2009
  Adam Tauno Williams <awilliam@whitemice.org>
  License: MIT/X11

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.

 ---
 The sole purpose of the RpcWasher is to verify the structure of 
 XmlRpcStruct objects and turn then into Dictionary objects
 or arrays of XmlRpcStructs into Lists of Dictionaries.  It is
 the role of RpcWasher to throw ZOGIProtocolExceptions if the
 structure of inbound data is illegal;  no other code should
 produce ZOGI protocol exceptions.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using CookComputing.XmlRpc;

namespace Whitemice.ZOGI.RPC2
{  

  public delegate void Warning(object message);
  
  /// <brief>
  /// Clean up and sanatize the server responses before turning them over
  /// to the factory.  This class should absorb the protocol anomalies,
  /// bugs, and version differences.  Class is static for simplicity and
  /// to avoid any threading issues.
  /// </brief>
  public static class RpcWasher
  {
    public static Warning  warning;

    public static IList<IDictionary<string,object>>Process(Array _structs)
    {
      IList<IDictionary<string,object>> structs;

      structs = new List<IDictionary<string,object>>();
      foreach(object tmp in _structs)
      {
        if (tmp is XmlRpcStruct)
        {
          structs.Add(Process(tmp as XmlRpcStruct));
        } else if (tmp is System.Object[])
          {
            // TODO:  Why and when does this happen?
            System.Object[] a = (tmp as System.Object[]);
            
            for (int i = 0; i < a.Length; i++)
            {
              if (a[i] is XmlRpcStruct)
              {
                XmlRpcStruct x = (a[i] as XmlRpcStruct);
                structs.Add(RpcWasher.Process(x));
              } else throw new NonStructInResultsException(tmp.GetType());
            }
          } else throw new NonStructInResultsException(tmp.GetType());
      } // end foreach
      return structs;
    } // end Proces(Array)

    /// <summary>
    /// Turn the provided XmlRpcStruct into a santized dictionary.
    /// </summary>
    /// <param name="_struct">
    /// A <see cref="CookComputing.XmlRpc.XmlRpcStruct"/>
    /// </param>
    /// <returns>
    /// A <see cref="System.Collections.Generic.IDictionary"/>
    /// </returns>  
    public static IDictionary<string,object> Process(XmlRpcStruct _struct)
    {
      string                      key;
      int                         version;
      Dictionary<string, object>  rpcStruct;
      
      rpcStruct = new Dictionary<string,object>();
      /// A valid structure must contain an entity name
      if (!(_struct.ContainsKey("entityName")))
        throw new EntityWithNoEntityNameException();
      foreach(object tmp in _struct.Keys)
      {
        if (!(tmp is System.String))
          throw new NonStringKeyException(tmp);
        key = (tmp as System.String);
        if (_struct[tmp] == null)
          throw new NullValueForKeyException(key);
        switch (key)
        {
          case "objectId":
            rpcStruct.Add("objectId", ProcessObjectId(_struct[key]));
            break;
          case "version":
            if (_struct["version"] is System.Int32)
              version = ((int)_struct["version"]);
            else throw new NonIntegerVersionException(_struct["version"].GetType());
            rpcStruct.Add("version", version);
            break;
          case "_DEFAULTS":
            Console.WriteLine("Special processing of _DEFAULTS");
            /// The defaults entity is the one case where the contents of
            /// a subordinate key ("_") attribute is not an array,  the
            /// defaults entity is always the struct directly referenced
            /// by the key. So here we transform this protocol anolamly 
            /// into a single element array so the factory can work it
            /// without having a hack - the purpose of this object is to
            /// absorb all protocol hacks.
            ArrayList                  ar = new ArrayList();
            ar.Add(RpcWasher.Process(_struct[key] as XmlRpcStruct));
            rpcStruct.Add(key, ar);
            break;
          default:
            /// Perform general processing of keys;  keys that start with "_"
            /// are assummed to be subordinate keys, and everything else
            /// is assumed to be a raw value.
            if (key.StartsWith("_"))
            {
              IList<IDictionary<string,object>> children;
              if (_struct[key] is Array)
              {
                children = RpcWasher.Process(_struct[key] as Array);
                rpcStruct.Add(key, (IEnumerable)children); 
              } else throw new NonArraySubordinateKeyException(key);
            } else rpcStruct.Add(key, _struct[key]);
           break;
        }
      } // end foreach
      // add required fields if absent
      if (!(rpcStruct.ContainsKey("version")))
        rpcStruct.Add("version", 0);
      return rpcStruct;
    } // end Process(XmlRpcStruct)
    
    /// <summary>
    /// Output to standard out the provided structure,  this is called whenever
    /// a broken structutr involved.
    /// </summary>
    /// <param name="_struct">
    /// A <see cref="System.Collections.Generic.IDictionary"/>
    /// </param>
    /// <param name="_indent">
    /// A <see cref="System.Int32"/>
    /// </param>
    /// <returns>
    /// A <see cref="System.String"/>
    /// </returns>
    public static void Render(IDictionary<string,object> _struct)
    {
			try
			{
	  		foreach(string key in _struct.Keys)
	  		  Console.WriteLine("Key {0} = {1}({2})", key, 
                                                  _struct[key].ToString(),
                                                  _struct[key].GetType());
	    } catch (Exception exception)
	      {
	        throw new Exception("Exception rendering RPC result", exception);
	      }
    } // end Render(Hashtable)

    public static int ProcessObjectId(object _objectId)
    {
      /// Handle objectId (must be an int)
      if (_objectId is System.Int32)
        return Convert.ToInt32(_objectId);
      if (_objectId is XmlRpcStruct)
      {
        XmlRpcStruct x = (_objectId as XmlRpcStruct);
        if (x.ContainsKey("entityName") && x.Contains("objectId"))
        {
          if (x.ContainsKey("keyValues"))
          {
            Array a = (x["keyValues"] as Array);
            foreach(object kv in a)
              if (kv is System.Int32)
                return (int)kv;
          }
        }
        throw new NonIntegerObjectIdException(_objectId);
      }
      if (_objectId is System.String)
      {
        try 
        { 
          int x = Convert.ToInt32(_objectId);
          return x;
        } catch(Exception _exception)
          {
            throw new NonIntegerObjectIdException(_objectId, _exception);
          }
      }
      // process has failed if it reaches the end of the function
      throw new NonIntegerObjectIdException(_objectId);
    } // end ProcessObjectId
  }
}
